﻿#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2008 Jonathan Skeet. All rights reserved.
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// These methods © 2013 Jon Hanna. Contributed to the above project.
// Licenses under the Apache License, Version 2.0 as per above.
#endregion
namespace MoreLinq
{
    using System;
    using System.Collections.Generic;
    public static partial class MoreEnumerable
    {
        /// <summary>
        /// Attempts to retrieve the first item from a sequence.
        /// </summary>
        /// <param name="source">The source sequence.</param>
        /// <param name="value">The value found, or the default for the type
        /// (null for reference types) if none is found.</param>
        /// <returns>True if an item is found, false otherwise.</returns>
        public static bool TryFirst<TSource>(this IEnumerable<TSource> source, out TSource value)
        {
            if (source == null) throw new ArgumentNullException("source");
            IList<TSource> asList = source as IList<TSource>;
            if(asList != null)//enough of a boost to be worth the check, but does this come up often enough?
            {
                if(asList.Count != 0)
                {
                    value = asList[0];
                    return true;
                }
            }
            else
                using(var en = source.GetEnumerator())
                    if(en.MoveNext())
                    {
                        value = en.Current;
                        return true;
                    }
            value = default(TSource);
            return false;
        }
        /// <summary>
        /// Attempts to retrieve the first item from a sequence that matches a predicate.
        /// </summary>
        /// <param name="source">The source sequence.</param>
        /// <param name="predicate">The predicate that the item must fulfil.</param>
        /// <param name="value">The value found, or the default for the type
        /// (null for reference types) if none is found.</param>
        /// <returns>True if an item is found, false otherwise.</returns>
        public static bool TryFirst<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, out TSource value)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (predicate == null) throw new ArgumentNullException("predicate");
            foreach(TSource item in source)
                if(predicate(item))
                {
                    value = item;
                    return true;
                }
            value = default(TSource);
            return false;
        }
        /// <summary>
        /// Attempts to retrieve the last item from a sequence.
        /// </summary>
        /// <param name="source">The source sequence.</param>
        /// <param name="value">The value found, or the default for the type
        /// (null for reference types) if none is found.</param>
        /// <returns>True if an item is found, false otherwise.</returns>
        public static bool TryLast<TSource>(this IEnumerable<TSource> source, out TSource value)
        {
            if (source == null) throw new ArgumentNullException("source");
            IList<TSource> asList = source as IList<TSource>;
            if(asList != null)//enough of a boost to be worth the check.
            {
                int count = asList.Count;
                if(count != 0)
                {
                    value = asList[asList.Count - 1];
                    return true;
                }
            }
            else
                using(var en = source.GetEnumerator())
                    if(en.MoveNext())
                    {
                        TSource current;//don't assign straight to value
                                        //aside from some minor perf. improvements
                                        //in some cases, it can lead to some strangeness
                                        //in multi-threaded scenarios.
                        do
                        {
                            current = en.Current;
                        }while(en.MoveNext());
                        value = current;
                        return true;
                    }
            value = default(TSource);
            return false;
        }
        /// <summary>
        /// Attempts to retrieve the last item from a sequence that matches a predicate.
        /// </summary>
        /// <param name="source">The source sequence.</param>
        /// <param name="predicate">The predicate that the item must fulfil.</param>
        /// <param name="value">The value found, or the default for the type
        /// (null for reference types) if none is found.</param>
        /// <returns>True if an item is found, false otherwise.</returns>
        public static bool TryLast<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, out TSource value)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (predicate == null) throw new ArgumentNullException("predicate");
            TSource current = default(TSource);
            bool found = false;
            foreach(TSource item in source)
                if(predicate(item))
                {
                    current = item;
                    found = true;
                }
            value = current;
            return found;
        }
        /// <summary>
        /// Attempt to retrieve the value at a given index, reporting upon success.
        /// </summary>
        /// <param name="source">The source sequence</param>
        /// <param name="index">The zero-based index of the item sought.</param>
        /// <param name="value">The item found, or the default value (null for reference types)
        /// if the index was out of range.</param>
        /// <returns>True if the item was found, false if the index was out of range.</returns>
        public static bool TryElementAt<TSource>(this IEnumerable<TSource> source, int index, out TSource value)
        {
            if (source == null) throw new ArgumentNullException("source");
            if(index >= 0)
            {
                IList<TSource> asList = source as IList<TSource>;
                if(asList != null)
                {
                    if(index < asList.Count)
                    {
                        value = asList[index];
                        return true;
                    }
                }
                else
                    using(var en = source.GetEnumerator())
                        while(en.MoveNext())
                        {
                            if(index == 0)
                            {
                                value = en.Current;
                                return true;
                            }
                            --index;
                        }
            }
            value = default(TSource);
            return false;
        }
        /// <summary>
        /// Attempts to retrieve the single value in the sequence.
        /// </summary>
        /// <param name="source">The sequence to retrived the value from.</param>
        /// <param name="value">The value, if it was the sole value.</param>
        /// <returns>True if the value was obtained, false if there were no elements,
        /// or if there were more than one.</returns>
        public static bool TrySingle<TSource>(this IEnumerable<TSource> source, out TSource value)
        {
            if (source == null) throw new ArgumentNullException("source");
            IList<TSource> asList = source as IList<TSource>;
            if(asList != null)
            {
                if(asList.Count == 1)
                {
                    value = asList[0];
                    return true;
                }
            }
            else
                using(var en = source.GetEnumerator())
                    if(en.MoveNext())
                    {
                        var cur = en.Current;
                        if(!en.MoveNext())
                        {
                            value = cur;
                            return true;
                        }
                    }
            value = default(TSource);
            return false;
        }
        /// <summary>
        /// Attempts to retrieve the single value in the sequence that matches
        /// a predicate.
        /// </summary>
        /// <param name="source">The sequence to retrived the value from.</param>
        /// <param name="predicate">The predicate to test the elements against.</param>
        /// <param name="value">The value, if it was the sole matching value.</param>
        /// <returns>True if the value was obtained, false if there were no elements,
        /// or if there were more than one.</returns>
        public static bool TrySingle<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, out TSource value)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (predicate == null) throw new ArgumentNullException("predicate");
            using(var en = source.GetEnumerator())
                while(en.MoveNext())
                {
                    TSource cur = en.Current;
                    if(predicate(cur))
                    {
                        while(en.MoveNext())
                            if(predicate(en.Current))
                            {
                                //Not using goto here is more superstition than anything else.
                                value = default(TSource);
                                return false;
                            }
                        value = cur;
                        return true;
                    }
                }
            value = default(TSource);
            return false;
        }
    }
}